#include "FFGLBMD.h"

#define FFPARAM_CameraIndex (0)
#define FFPARAM_VideoModeIndex (1)

////////////////////////////////////////////////////////////////////////////////////////////////////
//  Plugin information
////////////////////////////////////////////////////////////////////////////////////////////////////

static CFFGLPluginInfo PluginInfo (
								   FFGLBMD::CreateInstance,			// Create method
								   "DADA FFGL_BMD",						// Plugin unique ID
								   "FAFA FFGLBMD",						// Plugin name
								   1,								// API major version number
								   000,								// API minor version number
								   1,								// Plugin major version number
								   000,								// Plugin minor version number
								   FF_SOURCE,						// Plugin type
								   "DADA FFGL BMD plugin",				// Plugin description
								   "DADA by Ilias Bergstrom - www.onar3d.com" // About
								   );


////////////////////////////////////////////////////////////////////////////////////////////////////
//  Constructor and destructor
////////////////////////////////////////////////////////////////////////////////////////////////////

FFGLBMD::FFGLBMD() : CFreeFrameGLPlugin() {
	// Input properties
	SetMinInputs(0);
	SetMaxInputs(0);
	SetTimeSupported(true);
	
    m_CameraIndex = 0;
	m_VideoModeIndex = 14;
	// Parameters
	// FF_TYPE_TEXT
	SetParamInfo(FFPARAM_CameraIndex,	"CameraIndex",		FF_TYPE_STANDARD, 0.0f);
	SetParamInfo(FFPARAM_VideoModeIndex, "VideoModeIndex",	FF_TYPE_STANDARD, 0.0f);
	
	m_Init = false;
}

FFGLBMD::~FFGLBMD() {
	fprintf(stderr, "FFGLBMD DELETED!\n");
}

void FFGLBMD::bail() {
	m_Camera1->stopCapture();
	
	if (this->m_DeckLink != NULL) {
        this->m_DeckLink->Release();
        this->m_DeckLink = NULL;
    }
	
    if (this->m_SelectedDecklinkDevice != NULL) {
        this->m_SelectedDecklinkDevice->Release();
        this->m_SelectedDecklinkDevice = NULL;
    }
	
    if (this->m_Camera1 != NULL) {
        delete m_Camera1;
		m_Camera1 = NULL;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
//  Methods
////////////////////////////////////////////////////////////////////////////////////////////////////

DWORD FFGLBMD::GetParameter(DWORD dwIndex) {
	DWORD dwRet;
	
	switch (dwIndex) {
		case FFPARAM_CameraIndex:
			*((float *)(unsigned)&dwRet) = m_CameraIndex;
			return dwRet;
		case FFPARAM_VideoModeIndex:
			*((float *)(unsigned)&dwRet) = m_VideoModeIndex;
			return dwRet;
			
		default:
			return FF_FAIL;
	}
}

DWORD FFGLBMD::SetParameter(const SetParameterStruct* pParam) {
	if (pParam != NULL) {
		switch (pParam->ParameterNumber) {
			case FFPARAM_CameraIndex:
				m_CameraIndex = *((float *)(unsigned)&(pParam->NewParameterValue));
				break;
			case FFPARAM_VideoModeIndex:
				m_VideoModeIndex = *((float *)(unsigned)&(pParam->NewParameterValue));
				break;
				
			default:
				return FF_FAIL;
		}
		
		return FF_SUCCESS;
	}
	
	return FF_FAIL;
}

// Buffer used by the default implementation of getParameterDisplay
static char s_DisplayValue[5];

char* FFGLBMD::GetParameterDisplay(DWORD dwIndex) {
	DWORD dwType = m_pPlugin->GetParamType(dwIndex);
	DWORD dwValue = m_pPlugin->GetParameter(dwIndex);
	
	if ((dwValue != FF_FAIL) && (dwType != FF_FAIL)) {
		if (dwType == FF_TYPE_TEXT) {
			return (char *)dwValue;
		}
		else {
			float fValue;
			memcpy(&fValue, &dwValue, 4);
			memset(s_DisplayValue, 0, 5);
			sprintf(s_DisplayValue, "%f", fValue);
			return s_DisplayValue;
		}
	}
	return NULL;
}

DWORD FFGLBMD::InitGL(const FFGLViewportStruct *vp) {
	vertexShaderCode =
	"void main()"
	"{"
	"	 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;"
	"  gl_TexCoord[0] = gl_MultiTexCoord0;"
	"  gl_FrontColor = gl_Color;"
	"}";
	
	fragmentShaderCode =
	"#version 120 \n"
	"uniform sampler2D UYVYtex; \n"		// UYVY macropixel texture passed as RGBA format
	"uniform int width;"
	"void main(void) \n"
	"{\n"
	"	float tx, ty, Y, Cb, Cr, r, g, b; \n"
	"	tx = gl_TexCoord[0].x; \n"
	"	ty = gl_TexCoord[0].y; \n"
	
	// The UYVY texture appears to the shader with 1/2 the true width since we used RGBA format to pass UYVY
	//"	int true_width = textureSize(UYVYtex, 0).x * 2; \n"
	"	int true_width = width; \n"
	
	// For U0 Y0 V0 Y1 macropixel, lookup Y0 or Y1 based on whether
	// the original texture x coord is even or odd.
	"	if (fract(floor(tx * true_width + 0.5) / 2.0) > 0.0) \n"
	"		Y = texture2D(UYVYtex, vec2(tx,ty)).a; \n"		// odd so choose Y1
	"	else \n"
	"		Y = texture2D(UYVYtex, vec2(tx,ty)).g; \n"		// even so choose Y0
	"	Cb = texture2D(UYVYtex, vec2(tx,ty)).b; \n"
	"	Cr = texture2D(UYVYtex, vec2(tx,ty)).r; \n"
	
	// Y: Undo 1/256 texture value scaling and scale [16..235] to [0..1] range
	// C: Undo 1/256 texture value scaling and scale [16..240] to [-0.5 .. + 0.5] range
	"	Y = (Y * 256.0 - 16.0) / 219.0; \n"
	"	Cb = (Cb * 256.0 - 16.0) / 224.0 - 0.5; \n"
	"	Cr = (Cr * 256.0 - 16.0) / 224.0 - 0.5; \n"
	// Convert to RGB using Rec.709 conversion matrix (see eq 26.7 in Poynton 2003)
	"	b = Y + 1.5748 * Cr; \n"
	"	g = Y - 0.1873 * Cb - 0.4681 * Cr; \n"
	"	r = Y + 1.8556 * Cb; \n"
	
	// Set alpha to 0.7 for partial transparency when GL_BLEND is enabled
	"	gl_FragColor = vec4(r, g, b, 1.0); \n"
	"}\n";

	
	///////////////////////////////////////////////
	
	m_extensions.Initialize();
	
	if (m_extensions.multitexture==0 || m_extensions.ARB_shader_objects==0)
		return FF_FAIL;
	
	m_InW = vp->width;
	m_InH = vp->height;
	
	m_shader.SetExtensions(&m_extensions);
	m_shader.Compile(vertexShaderCode,fragmentShaderCode);
	
	//activate our shader
	m_shader.BindShader();
	
	//to assign values to parameters in the shader, we have to lookup
	//the "location" of each value.. then call one of the glUniform* methods
	//to assign a value
	m_inputTextureLocation = m_shader.FindUniform("UYVYtex");
	
	//the 0 means that the 'inputTexture' in
	//the shader will use the texture bound to GL texture unit 0
	m_extensions.glUniform1iARB(m_inputTextureLocation, 0);
	
	// This is hardcoded for now...
	m_extensions.glUniform1fARB(m_dimensionsLocation, 1280);
	
	m_shader.UnbindShader();
	
	//create/upload the heat texture on texture unit 0
	//(which should already be the active unit)
	glGenTextures( 1, &m_bmdTextureId );
	glBindTexture(GL_TEXTURE_2D, m_bmdTextureId);
    
	//use bilinear interpolation when the texture is scaled larger
	//than its true size
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	
	//no wrapping (for when texture coordinates reference outside the
	//bounds of the texture)
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	
	//this allocates room for the gl texture, but doesn't fill it with any pixels
	//(the NULL would otherwise contain a pointer to the texture data)
	glTexImage2D(GL_TEXTURE_2D,
				 0, 4, //we are using a 32bit image, which has 4 bytes per pixel
				 1280,
				 720,
				 0, GL_RGBA,
				 GL_UNSIGNED_BYTE,
				 NULL);
	
	// Ilias setting up Blackmagic
    bool initialized    = false;
    bool selectMode     = false;
    bool startCapture   = false;
	
	m_CameraIndex = 0;
	m_VideoModeIndex = 12;
	
    // Connecting to Blackmagic and starting the stream
    int cameraPicked = pickDevice(m_CameraIndex);
	
    m_Camera1 = new CameraDecklink();
	
    initialized  = m_Camera1->initializeCamera(m_SelectedDecklinkDevice);
    selectMode   = m_Camera1->fetchSelectedDisplayMode(m_VideoModeIndex);
    startCapture = m_Camera1->startCapture();
	
    if(initialized  && selectMode && startCapture) {
		
	}
    else {
        bail();
	}

	m_Init = true;
	
	return FF_SUCCESS;
}

DWORD FFGLBMD::DeInitGL() {
	fprintf(stderr, "Inside DeInitGL\n");

	m_Init = false;
	bail();

	m_shader.FreeGLResources();
	
	if (m_bmdTextureId) {
		glDeleteTextures(1, &m_bmdTextureId);
		m_bmdTextureId = 0;
	}
	
	fprintf(stderr, "Inside DeInitGL #2\n");

	return FF_SUCCESS;
}

DWORD FFGLBMD::SetTime(double time) {
	m_curTime = time;
	return FF_SUCCESS;
}

//static int updated = 0;

DWORD FFGLBMD::ProcessOpenGL(ProcessOpenGLStruct *pGL) {
	glClear(GL_COLOR_BUFFER_BIT );
	
	glEnable(GL_TEXTURE_2D);
	
	glDisable(GL_BLEND);
	
	if(m_Init && (m_Camera1 != NULL)) {
		glColor4f(1.f,1.f,1.f,1.f);
	
		if(m_Camera1->isRunning()) {
			//bind the heat texture to texture unit 0
			glBindTexture(GL_TEXTURE_2D, m_bmdTextureId);
			
			m_extensions.glActiveTexture(GL_TEXTURE0);
			
			if (m_Camera1->FrameHasBeenUpdated()) {
				if( pthread_mutex_trylock(&m_Camera1->m_Delegate->framebytesMutex) ) {
				    frameBytes = m_Camera1->getFrameBytes();
				
					//upload it to the gl texture. use subimage because
					//the video frame size is probably smaller than the
					//size of the texture on the gpu hardware
					glTexSubImage2D(
							GL_TEXTURE_2D,
							0,
							0, 0,
							1280, //Texture.Width,
							720, //Texture.Height,
							GL_RGBA,
							GL_UNSIGNED_BYTE,					
                            frameBytes);
				
					pthread_mutex_unlock(&m_Camera1->m_Delegate->framebytesMutex);
				}
			}
				
		//	else{
		//		updated++;
		//		fprintf(stderr, "Frame has not been updated: %i!\n", updated);

		//	}
					
			//glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);

			//get the max s,t that correspond to the
			//width,height of the used portion of the allocated texture space
			//normalized (0..1) S and T coords
			GLdouble s = 1;
			GLdouble t = 1;
			
			s = s/2;
			t = t/2;
			
			//activate our shader
			m_shader.BindShader();
	
			glBegin(GL_QUADS);
		
			//lower left
			glTexCoord2d(s, t);
			glVertex2f(-1,-1);
			
			//upper left
			glTexCoord2d(s, 0.0);
			glVertex2f(-1,1);
			
			//upper right
			glTexCoord2d(0.0, 0.0);
			glVertex2f(1,1);
			
			//lower right
			glTexCoord2d(0.0, t);
			glVertex2f(1,-1);
			
			glEnd();
			
			//unbind the texture
			glBindTexture(GL_TEXTURE_2D, 0);
			
			m_shader.UnbindShader();
		}
		
		//restore the default matrix value
		//(default matrix value in OpenGL is the identity matrix)
		glLoadIdentity();
	}
	
	return FF_SUCCESS;
}

int FFGLBMD::pickDevice(int cameraIndex) {
	IDeckLinkIterator* deckLinkIterator = CreateDeckLinkIteratorInstance();
	
	if (!deckLinkIterator) {
		fprintf(stderr, "This application requires that DeckLink drivers are installed.\n");
	}
	
	/* Connect to the DeckLink instance */
	int cameraCount = 0;
	while(deckLinkIterator->Next(&m_DeckLink) == S_OK) {
		
		CFStringRef deviceName;
		m_DeckLink->GetDisplayName(&deviceName);
//		QString     dName       = toQString(deviceName);
//		QByteArray  byteArray1  = dName.toUtf8();
//		const char* cString1    = byteArray1.constData();
		
		CFStringRef modelName;
		m_DeckLink->GetModelName(&modelName);
//		QString     mName       = toQString(modelName);
//		QByteArray  byteArray2  = mName.toUtf8();
//		const char* cString2    = byteArray2.constData();
		
//		fprintf(stderr, "Device name: %s,", cString1);
//		fprintf(stderr, "Device type: %s \n", cString2);
		
		CFRelease(deviceName);
		CFRelease(modelName);
		
		if(cameraIndex == cameraCount) {
			m_SelectedDecklinkDevice = m_DeckLink;
			deckLinkIterator->Release();
			return cameraIndex;
		}
		
		m_DeckLink->Release();
		cameraCount++;
	}
	
	if (cameraCount == 0) {
		fprintf(stderr, "No Blackmagic devices found.\n");
	}
	
	deckLinkIterator->Release();
	return -1;
}
